package edu.uwm.cs552;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import edu.uwm.cs552.util.FormatException;
import edu.uwm.cs552.util.Pair;

/**
 * A coordinate on a hexagonal game board. There are two axes: across and down.
 * The hexagons are slanted to the left as they go down. Thus hexagon (0,0) is
 * adjacent to hexagon (1,1), but hexagon (1,0) is <em>not</em> adjacent to
 * (0,1).
 */
public class HexCoordinate implements HasTopography<Terrain> {
	private final int a, b;

	private Terrain terrain;

	/**
	 * Create a hex coordinate with across = a, down = b.
	 * 
	 * @param a
	 *            across
	 * @param b
	 *            down
	 */
	public HexCoordinate(int a, int b) {
		this.a = a;
		this.b = b;
	}

	public int a() {
		return a;
	}

	public int b() {
		return b;
	}

	/**
	 * Compute how many steps are needed to go from one hex coordinate to
	 * another.
	 * 
	 * @param other
	 *            coordinate to get to, must not be null.
	 * @return the distance from this coordinate to the other
	 */
	public int distance(HexCoordinate other) {
		int da = Math.abs(a - other.a);
		int db = Math.abs(b - other.b);
		int dc = Math.abs((a - b) - (other.a - other.b));
		// As it happens, (the margin is too small for the proof),
		// the sum of the two smallest differences is the same
		// as the largest difference.
		return (da + db + dc) / 2;
	}

	/**
	 * Return the hex coordinate that lies one segment along the given
	 * direction.
	 * 
	 * @param d
	 *            direction to move, must not be null
	 * @return hex coordinate in the given direction.
	 */
	public HexCoordinate move(HexDirection d) {
		return new HexCoordinate(d.da() + a, d.db() + b);
	}

	private static final double SQRT32 = Math.sqrt(3.0) / 2.0;

	static Point toPoint(double a, double b, double scale) {
		double x = a - b / 2.0;
		double y = SQRT32 * b;
		return new Point((int) Math.round(scale * x), (int) Math.round(scale
				* y));
	}

	/**
	 * Locate the center of the hexagon at this coordinate within the AWT pixel
	 * space, relative to the given scale.
	 * 
	 * @param scale
	 *            distance between centers of adjacent hexagons in pixels
	 * @return center of hexagon at this coordinate.
	 */
	@Override
	public Point toPoint(double scale) {
		return toPoint(a, b, scale);
	}

	/**
	 * Return a string of the form &lt;a,b&gt;.
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "<" + a + "," + b + ">";
	}

	/**
	 * @param s
	 * @return
	 * @throws FormatException
	 */
	public static HexCoordinate fromString(String s) throws FormatException {
		if (!s.startsWith("<") || !s.endsWith(">")) {
			throw new FormatException(
					"must be enclosed in angle brackets <>: '" + s + "'");
		}
		s = s.substring(1, s.length() - 1);
		int c = s.indexOf(',');
		if (c == -1) {
			throw new FormatException(
					"must have a comma between angle brackets: '<" + s + ">'");
		}
		String as = s.substring(0, c);
		String bs = s.substring(c + 1);
		try {
			int a = Integer.parseInt(as);
			int b = Integer.parseInt(bs);
			return new HexCoordinate(a, b);
		} catch (NumberFormatException e) {
			throw new FormatException(e);
		}
	}

	@Override
	public boolean equals(Object o) {
		if (!(o instanceof HexCoordinate))
			return false;
		HexCoordinate h = (HexCoordinate) o;
		return a == h.a && b == h.b;
	}

	@Override
	public int hashCode() {
		return a * 12797 + b;
	}

	public static final double SQRT3 = Math.sqrt(3);
	public static final double SQRT33 = Math.sqrt(3) / 3;

	/**
	 * Returns the coordinate of the hexagon that includes this point. If the
	 * point is on the edge of two or more hexagons; the result is an arbitrary
	 * choice.
	 * 
	 * @param p
	 *            point in the AWT pixel space
	 * @param scale
	 *            distance between centers of hexagons
	 * @return coordinate of hexagon that includes the point.
	 */
	public static HexCoordinate fromPoint(Point p, double scale) {
		// Help from http://playtechs.blogspot.com/2007/04/hex-grids.html
		double ay = Math.floor(-2 * p.x / scale);
		double ax = Math.floor((p.x + SQRT3 * p.y) / scale);
		double by = ax;
		double bx = Math.floor((p.x - SQRT3 * p.y) / scale);
		// NB: we need floating arithmetic here because
		// Java int division truncates toward 0.
		int a = (int) Math.floor((ax - ay + 1) / 3);
		int b = (int) Math.floor((by - bx + 1) / 3);
		return new HexCoordinate(a, b);
	}

	public static final HexCoordinate ORIGIN = new HexCoordinate(0, 0);

	private static void addPoint(Polygon p, Point pt) {
		p.addPoint(pt.x, pt.y);
	}

	private static final double THIRD = 1.0 / 3.0;
	private static final double TWOTHIRDS = 2.0 / 3.0;

	@Override
	public Polygon toPolygon(double scale) {
		Polygon p = new Polygon();
		addPoint(p, toPoint(a - THIRD, b - TWOTHIRDS, scale));
		addPoint(p, toPoint(a + THIRD, b - THIRD, scale));
		addPoint(p, toPoint(a + TWOTHIRDS, b + THIRD, scale));
		addPoint(p, toPoint(a + THIRD, b + TWOTHIRDS, scale));
		addPoint(p, toPoint(a - THIRD, b + THIRD, scale));
		addPoint(p, toPoint(a - TWOTHIRDS, b - THIRD, scale));
		return p;
	}

	@Override
	public Terrain getTopography() {
		return terrain;
	}

	@Override
	public void setTopography(Terrain topography) {
		this.terrain = topography;
	}

	@Override
	public void draw(Graphics g, double scale, boolean showHidden) {
		terrain.draw(g, this, scale, showHidden);
	}

	@Override
	public Pair<Point, Point> toLineSegment(double scale) {
		return null;
	}

}
